package com.ydlclass;

import java.util.concurrent.atomic.LongAdder;

/**
 * 请求id生成器
 * @Author duWenJian
 * @Date 2023/11/2 16:10
 * @Version 1.0
 */
public class IdGenerator {
    /**
     * 单机版本的线程安全的id发号器，一旦变成集群状态，就需要使用分布式的id生成器
     * private static LongAdder longAdder = new LongAdder();
     *     public static long getId() {
     *             // 增加一个计数器的值
     *         longAdder.increment();
     *             // 返回计数器的总和
     *         return longAdder.sum();
     *     }
     */


    /**
     * 雪花算法 64位
     * 机房号(数据中心)：5个bit  32
     * 机器号(机器标识)：5个bit  32
     * 时间戳(long型 1970-01-01 00:00:00 到当前时间的毫秒数)：42bit  2^41-1
     * 64位太长，所以我们把时间戳的部分截断，32，这样就能保证在一个毫秒内生成的id是连续的
     * 序列号(同一毫秒内，同一个机器，同一个数据中心，序列号是递增的)：10bit  2^10-1
     */

    // 起始时间戳
    private static final long START_TIME = DateUtil.get("2022-1-1").getTime();
    // 机房号
    private static final long DATA_CENTER_BIT = 5L;
    // 机器号
    private static final long MACHINE_BIT = 5L;
    // 序列号
    private static final long SEQUENCE_BIT = 12L;

    // 机房最大值
    private static final long DATA_CENTER_MAX = (1 << DATA_CENTER_BIT) - 1;
    // 机器最大值
    private static final long MACHINE_MAX = (1 << MACHINE_BIT) - 1;
    // 序列号最大值
    private static final long SEQUENCE_MAX = (1 << SEQUENCE_BIT) - 1;

    // 时间戳(42) 机房号(5) 机器号(5) 序列号(12)
    // 101010101010101010101010101010101010101010 10101 01010 101010101010

    // 时间戳位移
    public static final long TIMESTAMP_LEFT = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
    // 机房号位移
    public static final long DATA_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    // 机器号位移
    public static final long MACHINE_LEFT = SEQUENCE_BIT;

    private long dataCenterId;

    private long machineId;

    private LongAdder sequenceId = new LongAdder();
    // 时钟回拨的问题，需要我们去处理
    private long lastTimestamp = -1L;

    public IdGenerator(long dataCenterId, long machineId) {
        // 判断传入的参数是否合法
        if (dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX) {
            throw new IllegalArgumentException("dataCenterId can't be greater than " + DATA_CENTER_MAX + " or less than 0");
        }
        this.dataCenterId = dataCenterId;
        this.machineId = machineId;
    }

    public long getId() {
        // 1、第一步：获取当前时间戳
        long currentTime = System.currentTimeMillis();
        long timestamp = currentTime - START_TIME;

        // 判断时钟回拨
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        //sequenceId 需要做特殊的处理，如果是同一毫秒内，则sequenceId需要自增
        if ( timestamp == lastTimestamp){
            sequenceId.increment();
            if (sequenceId.sum() > SEQUENCE_MAX) {
                // 同一毫秒内，序列号溢出，阻塞等待
                timestamp = getNaxtTimeStamp();
            }
        } else {
            sequenceId.reset();
        }
        lastTimestamp = timestamp;
        long squence = sequenceId.sum();
        return timestamp << TIMESTAMP_LEFT | DATA_CENTER_BIT << DATA_LEFT | MACHINE_BIT << MACHINE_LEFT | squence;
    }

    /**
     * 获取下一个时间戳
     * 时间戳是基于初始时间的时间差，以毫秒为单位
     * @return 下一个时间戳
     */
    private long getNaxtTimeStamp() {
        while (true) {
            // 计算当前时间与初始时间的时间差
            long timestamp = System.currentTimeMillis() - START_TIME;
            // 如果当前时间戳大于上一个时间戳 ,如果一样则继续循环
            if (timestamp > lastTimestamp) {
                // 更新上一个时间戳为当前时间戳
                lastTimestamp = timestamp;
                // 返回当前时间戳
                return timestamp;
            }
        }
    }


    public static void main(String[] args) {
        IdGenerator idGenerator = new IdGenerator(1,2);
        for (int i = 0; i < 1000000; i++) {
            System.out.println(idGenerator.getId());
        }
    }
}
